// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import AppController from './AppController';
import PROTOCOL_CONFIG from './config/protocolConfig';
import Logger from './Logger';
import NoDebugger from './NoDebugger';

const WEBSOCKET_PREFIX = 'wss://';
const DEFAULT_RECONNECT_TIMES = 15;
const INIT_VIRTUAL_DEVICE_STATE = 2048;

/**
 * 生成一个接入实例
 *
 * @param {string} containerId - 用于显示云手机应用的div的id。
 * @param {String} options.ip - 接入云手机使用ip地址。例：127.0.0.1
 * @param {String} options.port - 接入云手机使用端口。例：12345
 * @param {String} options.session_id - 用户本次接入的会话id。客户后台生成的32为uuid，中间无空格、无连接符。
 * @param {String} options.background_timeout - 用户接入时home的时长，用户离开手机界面，home切换到后台。
 * @param {String} options.available_playtime - 用户可以使用的时长，用户本次接入最大可玩时长。
 * @param {String} options.ticket - 256位随机数，客户后台生成的随机数，用于认证。
 * @param {String} options.aes_key - 生成用于本次接入过程中用到的对称密钥。
 * @param {String} options.auth_ts - 生成当前时间的时间戳,用于鉴权模块验签时校验时效性。
 * @param {string} options.user_id - 用户ID
 * @param {string} options.touch_timeout - 无操作超时, 超时后断开连接
 * @param {boolean} options.auto_rotate - 根据屏幕方向自动横竖屏
 * @param {Object} [options.media_config=null] - 多媒体参数
 * @param {boolean} [options.need_heartbeat=true] - 是否需要维持心跳，远程登录phone的场景不需要心跳
 * @param {boolean} [options.reconnect_times=15] - 断链后重连的次数，默认值以保证向前兼容
 */
class CPHCloudApp {
    constructor(containerId, options) {
        let defaults = {};
        // 参数校验
        let msg = this._verify(options);
        if (msg) {
            throw msg;
        } else {
            this.options = {...defaults, containerId, ...options};
            this._init_();
        }

        options._debug !== true && NoDebugger.protect();
    }

    static setAppController() {
        const userOptions = this.options;
        let connectURI = userOptions.connect_uri || '';
        let splitPos = connectURI.startsWith(WEBSOCKET_PREFIX) ? WEBSOCKET_PREFIX.length : 0;
        connectURI = connectURI.substr(splitPos);
        let options = {
            phoneIp: userOptions.ip,
            phonePort: String(userOptions.port),
            connectURI: connectURI || [userOptions.ip, userOptions.port].join(':'),
            sessionId: String(userOptions.session_id),
            backgroundTimeout: String(userOptions.background_timeout),
            availablePlayTime: String(userOptions.available_playtime),
            ticket: String(userOptions.ticket),
            aesKey: String(userOptions.aes_key),
            authTimeStamp: String(userOptions.auth_ts),
            containerId: userOptions.containerId,
            userId: String(userOptions.user_id || ''),
            /*global __IS_DEBUG__*/
            isDebug: __IS_DEBUG__,
            isMobile: /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent),
            supportAudio: true,
            sdkVersion: this.version,
            touchTimeout: String(Math.max(Number(userOptions.touch_timeout || 0), 0)),
            needHeatBeat: userOptions.need_heartbeat === undefined ? true : userOptions.need_heartbeat,
            reconnectTimes: userOptions.reconnect_times === undefined ? DEFAULT_RECONNECT_TIMES : userOptions.reconnect_times,
            libPath: userOptions.libPath,
            decoderType: userOptions.decoder_type,
            microPhoneOutputType: userOptions.microPhoneOutputType
        };
        options.autoRotate = userOptions.auto_rotate && options.isMobile;
        if (userOptions.media_config) {
            options.mediaConfig = userOptions.media_config;
        }

        return new AppController(options);
    }

    _init_() {
        /*global __APP_VERSION__*/
        this.version = __APP_VERSION__;
        const isMobile = /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent);
        let fullscreenContainer = document.getElementById('fullscreen-container');

        if (!isMobile) {
            fullscreenContainer.style.width = '100%';
            fullscreenContainer.style.height = '600px';
        } else {
            const clientWidth = document.documentElement.clientWidth;
            const clientHeight = document.documentElement.clientHeight;
            fullscreenContainer.style.width = `${clientWidth}px`;
            fullscreenContainer.style.height = `${clientHeight}px`;
        }

        this.appController = CPHCloudApp.setAppController.bind(this)();
        this.appController.start();
        window.addEventListener('unload', () => {
            this.exit();
        });
    }

    __defaultValidator (name, val, rule) {
        if (rule.startsWith('len')) {
            let subs = rule.split(':');
            if (subs.length === 2 && val.length !== Number(subs[1])) {
                return `${name}'length is ${subs[1]}.`;
            }

            if (subs.length === 3 && (val.length < Number(subs[1]) || val.length > Number(subs[2]))) {
                return `${name}'length is ${subs.splice(1)}.`;
            }
        }

        if (rule.startsWith('fn')) {
            let subs = rule.split(':');
            let fn = subs[1];
            if (!fn || !this[fn]) {
                return '';
            }

            return this[fn](val);
        }

        return '';
    };

    _verify(options) {
        let schema = {
            ip: ['require'],
            port: ['require'],
            session_id: ['require'],
            ticket: ['require'],
            aes_key: ['require', 'hex', 'len:32'],
            auth_ts: ['require'],
            touch_timeout: ['number'],
            media_config: ['fn:_verifyMediaConfig']
        };
        let msg = '';

        if (!options) {
            return '初始化CloudApp需要一个object类型参数';
        }

        let keys = Object.keys(options);
        for (let i = 0, kLen = keys.length; i < kLen; i++) {
            let name = keys[i];
            let rules = schema[name] || [];
            let val = options[name];
            let subs;
            let error;

            for (let j = 0, rLen = (rules || []).length; j < rLen; j++) {
                switch (rules[j]) {
                    case 'require':
                        !val && (msg = `${name} is required.`);
                        break;
                    case 'number':
                        isNaN(Number(val)) && (msg = `${name} is a number.`);
                        break;
                    case 'hex':
                        !/^[a-f0-9]*$/i.test(val) && (msg = `${name} is a hex string.`);
                        break;
                    case 'port':
                        (Number(val) < 0 || Number(val) > 65535) && (msg = `${name} is a port.`);
                        break;
                    case 'ip':
                        subs = val.split('.');
                        error = subs && subs.find(sub => (Number(sub) < 0 || Number(sub) > 255));
                        (subs.length !== 4 || error) && (msg = `${name} is a ip.`);
                        break;
                    default:
                        msg = this.__defaultValidator(name, val, rules[j]);
                        break;
                }

                if (msg) {
                    return msg;
                }
            }
        }

        return msg;
    }

    _verifyMediaConfig(config) {
        if (typeof config !== 'object') {
            return 'Media config should be an object.';
        }

        if ((config.virtual_width && !config.virtual_height) || (!config.virtual_width && config.virtual_height)) {
            return 'virtual_width and virtual_height should be configured at the same time if you need.';
        }

        let schema = {
            mute: [{
                type: 'number',
                valueMap: [0,1]
            }],
            fps: [{
                type: 'number',
                min: 10,
                max: 60
            },
                {
                    type: 'multiple',
                    value: 10
                }],
            bitrate: [{
                type: 'number',
                min: 1000000,
                max: 10000000
            }],
            virtual_width: [{
                type: 'number',
                min: 240,
                max: 4096
            },
                {
                    type: 'multiple',
                    value: 8
                }],
            virtual_height: [{
                type: 'number',
                min: 240,
                max: 4096
            },
                {
                    type: 'multiple',
                    value: 8
                }]
        };

        const keys = Object.keys(config);
        for (let i = 0, kLen = keys.length; i < kLen; i++) {
            const name = keys[i];
            const val = config[name];
            const rules = schema[name] || [];
            for (let j = 0, rLen = rules.length; j < rLen; j++) {
                let rule = rules[j];
                switch (rule.type) {
                    case 'number': {
                        let numberVal = Number(val);
                        if (isNaN(numberVal)) {
                            return `${name} is a number.`;
                        }

                        if (rule.min !== undefined && numberVal < rule.min) {
                            return `${name} should be larger than ${rule.min}.`;
                        }

                        if (rule.max !== undefined && numberVal > rule.max) {
                            return `${name} should be smaller than ${rule.max}.`;
                        }

                        if (rule.valueMap !== undefined && !rule.valueMap.includes(numberVal)) {
                            return `${name} must be 0 or 1.`;
                        }

                        break;
                    }

                    case 'multiple':
                        if (rule.value && Number(val) % rule.value !== 0) {
                            return `${name} should be a multiple of ${rule.value}.`;
                        }

                        break;
                    default:
                }
            }
        }

        return '';
    }

    /*
     * @return {number} 当前音量值，数值范围在 0 ~ 100
     */
    getVolume() {
        return this.appController.getVolume();
    }

    /*
     * @param {number} value 要设置的音量值，数值范围为 0 ~ 100
     */
    setVolume(value) {
        if (typeof value !== 'number') {
            return;
        }

        if (value < 0) {
            value = 0;
        } else if (value > 100) {
            value = 100;
        }

        this.appController.setVolume(value);
    }

    exit() {
        this.appController.exit();
        this.appController = null;
    }

    pause() {
        this.appController.pauseCloudPhone();
    }

    resume() {
        this.appController.resumeCloudPhone();
    }

    reconnect() {
        this.appController.reconnect();
    }

    on(eventName, callback) {
        if (eventName === 'logReceived') {
            Logger.onLogReceived(callback);
        } else {
            this.appController.on(eventName, callback);
        }
    }

    off(eventName, callback) {
        this.appController.off(eventName, callback);
    }

    setResolution(clarityVal) {
        this.appController.setResolution(clarityVal);
    }

    setMediaConfig(config) {
        const msg = this._verifyMediaConfig(config);
        if (msg) {
            return msg;
        }

        this.appController.setMediaConfig(config);
    }

    sendDataToCloudApp(data) {
        this.appController.sendDataToCloudApp(data);
    }
}

CPHCloudApp.RESOLUTIONS = {...PROTOCOL_CONFIG.RESOLUTIONS};

export default CPHCloudApp;
